#!/usr/bin/env python
# coding=utf-8

from __future__ import print_function
from PIL import Image
from tilemap import TileMap
from tiles import TileLoader
from pyqrcode import MakeQR
from itertools import groupby
from images2gif import writeGif
import os
import random
import sys
import argparse


def bottomup_sorter(items):
    """Sorts items ((x,y)->tile) bottomup (decreasing y)."""
    key =(lambda x: -x[0][1])
    return sorted(items.items(), key=key)

def dfs_sorter(tiles):
    """Sorts all tiles ((x,y)->tile) in random dfs order."""
    res = []
    visited = set()
    def neighbours(pos):
        dx = [-1,0,0,1]
        dy = [0,-1,1,0]
        x,y = pos
        res = [] 
        for i in range(4):
            npos = dx[i]+x, dy[i]+y
            if npos in tiles:
                res.append(npos)
        random.shuffle(res)
        return res
    def dfs(pos):
        if pos in visited: return
        visited.add(pos)
        res.append((pos, tiles[pos]))
        for p in neighbours(pos):
            dfs(p)
    positions = list(tiles.keys())
    random.shuffle(positions)
    for pos in positions:
        dfs(pos)
    return res

def split_black_white(tiles):
    def filter(items, condition):
        res = {}
        for k, v in items.items():
            if condition(v):
                res[k] = v
        return res
    black = filter(tiles, lambda x: x.is_black())
    white = filter(tiles, lambda x: not x.is_black())
    return black, white

def black_dfs_sorter(tiles):
    """Sorts black tiles ((x,y)->tile) in random dfs order."""
    black, white = split_black_white(tiles)
    return dfs_sorter(black)+white
    

def white_dfs_sorter(tiles):
    """Sorts white tiles ((x,y)->tile) in random dfs order."""
    black, white = split_black_white(tiles)
    return dfs_sorter(white)+black

def black_white_dfs_sorter(tiles):
    """Sorts black tiles ((x,y)->tile) in random dfs order,
    then sorts white items in random dfs order."""
    black, white = split_black_white(tiles)
    return dfs_sorter(black)+dfs_sorter(white)


sorters = {
        "bottomup": bottomup_sorter,
        "black_dfs": black_dfs_sorter,
        "white_dfs": white_dfs_sorter,
        "black_white_dfs":black_white_dfs_sorter,
        "dfs": dfs_sorter,
        }

class Tiler(object):
    def __init__(self, block_size=16, tileset="tetris", generate_movie=False, frame_step=1, *args, **kwargs): 
        self.block_size = block_size
        self.tileset = tileset
        self.generate_movie = generate_movie
        self.frame_step = frame_step

    def tile_qr_code(self, text, output_name="qr.png", movie_output_name="qr.gif", *args, **kwargs):
        while len(text) < 6:
            text += " "
        qrcode = MakeQR(text)
        bCount = qrcode.getModuleCount()
        tilemap = TileMap(qrcode.get_bwmap(), (bCount, bCount))
        key = (lambda x: -x.priority)
        loader = TileLoader(self.tileset)
        tiles = sorted(loader.tiles, key=key)
        
        usedTiles = {}
        ys = list(range(tilemap.dim[1]))

        for p, tls in groupby(tiles, key):
            tls = list(tls)
            for y in ys:
                xs = list(range(tilemap.dim[0]))
                for x in xs:
                    random.shuffle(tls)
                    for tile in tls:
                        if tile.matches(tilemap, (x,y)):
                            tile.use(tilemap, (x,y))
                            usedTiles[x,y] = tile
                            break
        
        qrImage = qrcode.make_image(block_in_pixels=self.block_size)
        image = Image.new("RGBA", qrImage.size, (255,255,255,255))
        for xy, tile in usedTiles.items():
            tile.draw(image, xy, self.block_size)
        if self.generate_movie:
            img = Image.new("RGBA", image.size, (255,255,255,255))
            images = [self.add_border(img)]
            used = sorters[loader.sorter](usedTiles)
            i = 1 
            for xy, tile in used:
                tile.draw(img, xy, self.block_size)
                if i % self.frame_step == 0:
                    images.append(self.add_border(img))
                if i % 50 == 0:
                    print("Generating movie: {0}/{1}".format(i, len(usedTiles)))
                i += 1
            images.append(self.add_border(img))
            print("Saving {0}".format(movie_output_name))
            writeGif("qr.gif", images, loops=1, duration=0.01*self.frame_step, dither=loader.dither)
        self.add_border(image).save(output_name, "PNG")
    def add_border(self, image):
        w,h = image.size
        b = self.block_size
        res = Image.new("RGBA", (w+2*b, h+2*b), (255,255,255,255))
        res.paste(image, (b,b))
        return res


def main():
    tilesets = sorted( name for name in os.listdir(os.path.dirname(os.path.abspath(__file__))) if os.path.isdir(name) and name[0] != '.')
    if not tilesets:
        sys.err.print("NO TILESETS FOUND!")
        sys.exit(1)
    default_tileset = tilesets[0]
    parser = argparse.ArgumentParser(description="Generate fancy QR code")
    parser.add_argument("text", help="Text to be encoded into QR code.")
    parser.add_argument("-t", "--tileset", help="Name of tileset to use (default: {0}).".format(default_tileset), choices=tilesets, default=default_tileset)
    parser.add_argument("-b", "--block-size", help="Block size in pixels (default: 16).", default=16, type=int)
    parser.add_argument("-m", "--generate-movie", help="Generate movie (gif file).", default=False, action="store_true")
    parser.add_argument("-o", "--output-name", help="Output file name (default: qr.png).", default="qr.png")
    parser.add_argument("-O", "--movie-output-name", help="Output file name for movie (default: qr.gif).", default="qr.gif")
    parser.add_argument("-f", "--frame-step", help="Determines how many blocks should appear on each frame (default=10).", default=10, type=int)
    args = vars(parser.parse_args())
    Tiler(**args).tile_qr_code(**args)

if __name__ == "__main__":
    main()
